﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace ReactiveGraph.Core
{
    public class RxCollection<T> : IList<T>
    {
        private readonly Subject<ChangedArgs<T>> _changed = new Subject<ChangedArgs<T>>();
        private readonly List<T> _innerList = new List<T>();

        public IObservable<ChangedArgs<T>> Changed
        {
            get { return _changed; }
        }

        #region IList<T> Members

        public virtual void Add(T item)
        {
            var startingIndex = _innerList.Count;
            _innerList.Add(item);
            OnCollectionChanged(ChangedArgs<T>.Added(startingIndex, item));
        }

        public bool Remove(T item)
        {
            var index = _innerList.IndexOf(item);
            var removed = _innerList.Remove(item);
            OnCollectionChanged(ChangedArgs<T>.Removed(index, item));
            return removed;
        }

        public void RemoveAt(int index)
        {
            var item = _innerList[index];
            _innerList.RemoveAt(index);
            OnCollectionChanged(ChangedArgs<T>.Removed(index, item));
        }

        public T this[int index]
        {
            get { return _innerList[index]; }
            set
            {
                var previous = _innerList[index];
                _innerList[index] = value;
                OnCollectionChanged(ChangedArgs<T>.Replace(index, new[] {value}, new[] {previous}));
            }
        }

        public void Clear()
        {
            var copy = _innerList.ToArray();
            _innerList.Clear();
            OnCollectionChanged(ChangedArgs<T>.Removed(0, copy));
        }

        public bool Contains(T item)
        {
            return _innerList.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            _innerList.CopyTo(array, arrayIndex);
        }

        public int IndexOf(T item)
        {
            return _innerList.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            _innerList.Insert(index, item);
        }

        public int Count
        {
            get { return _innerList.Count; }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            return _innerList.GetEnumerator();
        }

        public IEnumerator GetEnumerator()
        {
            return ((IEnumerable) _innerList).GetEnumerator();
        }

        #endregion

        protected virtual void OnCollectionChanged(ChangedArgs<T> changedArgs)
        {
            _changed.OnNext(changedArgs);
        }

        public virtual void AddRange(IEnumerable<T> range)
        {
            var startingIndex = _innerList.Count;
            _innerList.AddRange(range);
            OnCollectionChanged(ChangedArgs<T>.Added(startingIndex, range));
        }

        public virtual void AddRange(params T[] range)
        {
            AddRange(range as IEnumerable<T>);
        }
    }


    public class ChangedArgs<T>
    {
        private ChangedArgs()
        {
            AddedItems = Enumerable.Empty<T>();
            RemovedItems = Enumerable.Empty<T>();
        }

        public IEnumerable<T> AddedItems { get; private set; }
        public int StartingIndex { get; private set; }
        public IEnumerable<T> RemovedItems { get; private set; }

        public static ChangedArgs<T> Added(int startingIndex, params T[] added)
        {
            return Added(startingIndex, added as IEnumerable<T>);
        }

        public static ChangedArgs<T> Added(int startingIndex, IEnumerable<T> added)
        {
            return new ChangedArgs<T>
                       {
                           AddedItems = added,
                           StartingIndex = startingIndex
                       };
        }

        public static ChangedArgs<T> Removed(int startingIndex, params T[] removed)
        {
            return Removed(startingIndex, removed as IEnumerable<T>);
        }

        public static ChangedArgs<T> Removed(int startingIndex, IEnumerable<T> removed)
        {
            return new ChangedArgs<T>
                       {
                           RemovedItems = removed,
                           StartingIndex = startingIndex
                       };
        }

        public static ChangedArgs<T> Replace(int startingIndex, IEnumerable<T> added, IEnumerable<T> removed)
        {
            return new ChangedArgs<T>
                       {
                           AddedItems = added,
                           RemovedItems = removed,
                           StartingIndex = startingIndex
                       };
        }
    }
}